package com.mrx.commons.task;

import cn.hutool.cron.Scheduler;
import cn.hutool.cron.TaskExecutor;
import cn.hutool.cron.listener.TaskListener;
import com.mrx.commons.mail.MailHelper;
import com.mrx.commons.mail.model.MailDTO;
import com.mrx.commons.task.model.ScheduleTaskWrapper;
import com.mrx.commons.task.model.TaskCreateVO;
import com.mrx.commons.task.model.TaskLifeCycle;
import com.mrx.commons.task.model.XTask;
import com.mrx.commons.trace.TraceUtils;
import com.mrx.commons.utils.ThreadUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author Mr.X
 * @since 2023-12-08 13:42
 **/
public class ScheduleTaskService implements TaskListener {

    private static final ExecutorService pool = TraceUtils.newFixedTracerPool(100, "x-schedule-task");
    private static final List<TaskWrapper> tasks = new CopyOnWriteArrayList<>();
    private static final long MAX_EXECUTE_TIME = Duration.ofMinutes(1).toMillis();
    private static final Logger logger = LoggerFactory.getLogger(ScheduleTaskService.class);
    private static final Scheduler scheduler = new Scheduler();
    private volatile boolean scanTaskStarted = false;
    private final MailHelper mailHelper;

    public ScheduleTaskService(MailHelper mailHelper) {
        this.mailHelper = mailHelper;
    }

    public void createTask(TaskCreateVO createVO) {
        String corn = createVO.getCorn();
        if (!scanTaskStarted) {
            synchronized (this) {
                if (!scanTaskStarted) {
                    pool.submit(this::startScanTask);
                }
            }
        }
        XTask task = SchedulerTask.createTask(createVO);
        if (corn != null) {
            logger.debug("prepare to schedule task");
            scheduler.schedule(createVO.getName(), corn, ScheduleTaskWrapper.of(task));
            if (!scheduler.isStarted()) {
                scheduler.addListener(this);
                scheduler.setMatchSecond(true);
                scheduler.start();
            }
        }
    }

    @Override
    public void onStart(TaskExecutor taskExecutor) {
        ScheduleTaskWrapper task = (ScheduleTaskWrapper) taskExecutor.getTask();
        logger.debug("task: {} started", task.getTaskName());
        mailNotifyIfNeeded(task, TaskLifeCycle.STARTED);
    }

    @Override
    public void onSucceeded(TaskExecutor taskExecutor) {
        ScheduleTaskWrapper task = (ScheduleTaskWrapper) taskExecutor.getTask();
        logger.debug("task: {} succeed, taskLog: {}", task.getTaskName(), task.getResult());
        mailNotifyIfNeeded(task, TaskLifeCycle.SUCCEED);
    }

    @Override
    public void onFailed(TaskExecutor taskExecutor, Throwable throwable) {
        ScheduleTaskWrapper task = (ScheduleTaskWrapper) taskExecutor.getTask();
        logger.debug("task: {} succeed, taskLog: {}", task.getTaskName(), task.getResult());
        mailNotifyIfNeeded(task, TaskLifeCycle.FAILED);
    }

    public Future<String> runTask(String taskName) {
        Future<String> task = pool.submit(() -> SchedulerTask.runTask(taskName));
        tasks.add(TaskWrapper.of(task, taskName));
        return task;
    }

    private void startScanTask() {
        scanTaskStarted = true;
        logger.info("starting scanTask");
        while (true) {
            Iterator<TaskWrapper> iterator = tasks.iterator();
            while (iterator.hasNext()) {
                TaskWrapper task = iterator.next();
                if (task.isDone() || task.isCancelled()) {
                    iterator.remove();
                } else if (task.isTimeout()) {
                    logger.warn("task: {} execute timeout, task will be canceled", task.getTaskName());
                    boolean isCanceled = task.cancel();
                    logger.debug("task: {}, canceled: {}", task.getTaskName(), isCanceled);
                }
            }
            ThreadUtils.safeSleep(500, TimeUnit.MILLISECONDS);
        }
    }

    private void mailNotifyIfNeeded(ScheduleTaskWrapper taskWrapper, TaskLifeCycle status) {
        // 调用这个方法就清空一下执行结果
        Optional<String> result = taskWrapper.getAndClearResult();
        if (taskWrapper.mailNotify() && taskWrapper.notifyStatus().contains(status)) {
            if (mailHelper == null) {
                logger.warn("未注入 MailHelper, 请先配置 MailHelper, 否则任务邮件通知将不会生效");
                return;
            }
            String receiveMail = taskWrapper.notifyMail();
            if (!StringUtils.hasText(receiveMail)) {
                logger.warn("未设置邮件接收人, 不发送邮件");
                return;
            }
            String subject = "定时任务: " + taskWrapper.getTaskName() + " 当前处于 " + status.getDesc() + " 状态";
            MailDTO mail = MailDTO.builder()
                    .subject(subject)
                    .text(result.orElse(subject))
                    .sendTo(receiveMail)
                    .build();
            mailHelper.sendSimpleMail(mail);
        }
    }

    @Data
    @AllArgsConstructor(staticName = "of")
    private static final class TaskWrapper {
        private Future<?> task;
        private String taskName;
        private final long startTime = System.currentTimeMillis();

        public boolean cancel() {
            return task.cancel(true);
        }

        public boolean isCancelled() {
            return task.isCancelled();
        }

        public boolean isDone() {
            return task.isDone();
        }

        public boolean isTimeout() {
            return startTime + MAX_EXECUTE_TIME > System.currentTimeMillis();
        }

    }

}
